package com.xxl.job.executor.protocol;

import com.prosysopc.ua.*;
import com.prosysopc.ua.client.*;
import com.xxl.job.core.log.XxlJobLogger;
import org.opcfoundation.ua.builtintypes.DataValue;
import org.opcfoundation.ua.builtintypes.NodeId;
import org.opcfoundation.ua.builtintypes.UnsignedInteger;
import org.opcfoundation.ua.common.ServiceResultException;
import org.opcfoundation.ua.core.*;
import org.opcfoundation.ua.transport.security.SecurityMode;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;


public class OPCUAProtocol {

    private String serverUri;

    protected UaClient uaClient;

    protected static String APP_NAME = "OpcUaClient";

    protected NodeId nodeId = null;

    protected Subscription subscription;

    protected UnsignedInteger attributeId;

    private int sessionCount = 0;

    private MonitoredDataItemListener dataChangeListener;

    public OPCUAProtocol(){

    }

    public OPCUAProtocol(String serverUri){
        this.serverUri=serverUri;
    }

    public String getServerUri() {
        return serverUri;
    }

    public void setServerUri(String serverUri) {
        this.serverUri = serverUri;
    }

    public MonitoredDataItemListener getDataChangeListener() {
        return dataChangeListener;
    }

    public void setDataChangeListener(MonitoredDataItemListener dataChangeListener) {
        this.dataChangeListener = dataChangeListener;
    }

    public void connect(){

        try {
            uaClient=new UaClient(serverUri);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            XxlJobLogger.log("创建"+serverUri+" OPC客户端对象失败!"+e.getMessage());
        }
        uaClient.setSecurityMode(SecurityMode.NONE);
        try {
            uaClient.setUserIdentity(new UserIdentity());
        } catch (SessionActivationException e) {
            e.printStackTrace();
            XxlJobLogger.log("创建OPC Client用户身份认证失败"+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }

        //定义服务器中可见的会话名称
        uaClient.setSessionName(String.format("%s@%s Session%d", APP_NAME,
                ApplicationIdentity.getActualHostNameWithoutDomain(), ++sessionCount));

        try {
            uaClient.connect();
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("创建"+serverUri+" OPC连接失败!"+e.getMessage());
        }

    }

    /**
     * 浏览OPC Server内的地址空间
     * 此方法应根据实际节点层次逐次调用，直至访问到实际变量引用
     * @param browseName
     * @return
     */
    public  List<ReferenceDescription> browseReference(String browseName){

        //限制每次调用只处理 1000 个引用
        //uaClient.getAddressSpace().setMaxReferencesPerNode(1000);
        //设定只返回节点间的层次引用(hierarchical reference)
        uaClient.getAddressSpace().setReferenceTypeId(  Identifiers.HierarchicalReferences);
        //读取节点,从根节点开始
        nodeId= Identifiers.RootFolder;
        List<ReferenceDescription> references=null;
        try{
            references = uaClient.getAddressSpace().browse(nodeId);
        }catch (StatusException e){
            e.printStackTrace();
            XxlJobLogger.log("OPC Client浏览根节点失败，NodeId:"+nodeId.toString()+","+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }catch (ServiceException e){
            e.printStackTrace();
            XxlJobLogger.log("OPC Client浏览根节点失败，NodeId:"+nodeId.toString()+","+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }

        for(ReferenceDescription referenceDescription:references){
            if(browseName.equals(referenceDescription.getBrowseName())){
                try {
                    //转换nodeId
                    nodeId = uaClient.getAddressSpace().getNamespaceTable().toNodeId(referenceDescription.getNodeId());
                    break;
                } catch (ServiceResultException e) {
                    e.printStackTrace();
                    XxlJobLogger.log("OPC NodeId转换失败，browseName:"+browseName);
                    if(uaClient.isConnected()){
                        uaClient.disconnect();
                    }
                }
            }
        }
        try {
            references = uaClient.getAddressSpace().browse(nodeId);
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC 浏览下级节点失败，browseName:"+browseName);
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        } catch (StatusException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC 浏览下级节点失败，browseName:"+browseName);
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }

        return references;

    }

    /**
     * 创建订阅
     */
    public void createSubscription(List<ReferenceDescription> references){

        attributeId = Attributes.Value;
        subscription = new Subscription();

        List<MonitoredItem> items = new ArrayList<MonitoredItem>();

        for (ReferenceDescription r : references) {
            try {
                items.add(createMonitoredDataItem(uaClient.getNamespaceTable().toNodeId(r.getNodeId()), Attributes.Value));
            } catch (ServiceResultException e) {
                XxlJobLogger.log("OPC Client添加订阅单节点失败，节点为： "+r.getNodeId().toString()+" 异常为： " + e.getMessage());
                if(uaClient.isConnected()){
                    uaClient.disconnect();
                }
            }
        }
        try {
            subscription.addItems(items.toArray(new MonitoredItem[items.size()]));
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client添加订阅节点失败！异常为："+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }
        try {
            //订阅全部
            uaClient.addSubscription(subscription);
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client订阅全部变量失败！异常为："+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        } catch (StatusException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client订阅全部变量失败！异常为："+e.getMessage());
            if(uaClient.isConnected()){
                uaClient.disconnect();
            }
        }
    }

    /**
     * 创建数据变化监听过滤器
     * @param nodeId
     * @param attributeId
     * @Param dataChangeListener
     * @return
     */
    protected MonitoredDataItem createMonitoredDataItem(NodeId nodeId, UnsignedInteger attributeId) {
        MonitoredDataItem dataItem =
                new MonitoredDataItem(nodeId, attributeId, MonitoringMode.Reporting, subscription.getPublishingInterval());

        dataItem.setDataChangeListener(dataChangeListener);
        DataChangeFilter filter = new DataChangeFilter();
        filter.setDeadbandValue(1.00);
        filter.setTrigger(DataChangeTrigger.StatusValue);
        filter.setDeadbandType(UnsignedInteger.valueOf(DeadbandType.Percent.getValue()));
        return dataItem;
    }




//    /**
//     * 订阅数据示例,该变量应创建在业务端,创建完客户端连接后再浏览地址空间，然后对该变量按此示例set赋值，再开始创建订阅。
//     */
//    private MonitoredDataItemListener dataChangeListener =
//            new MonitoredDataItemListener() {
//                @Override
//                public void onDataChange(MonitoredDataItem sender, DataValue prevValue, DataValue value) {
//                    MonitoredItem monitoredItem = sender;
//                    //变量名称
//                    monitoredItem.getNodeId().toString().replace("ns=2;s=","");
//                    //状态码
//                    value.getStatusCode();
//                    //值
//                    value.getValue();
//                    System.out.println("上次值:"+prevValue.getValue());
//                    System.out.println("本次值:"+value.getValue());
//                    System.out.println("状态码:"+value.getStatusCode());
//                    System.out.println("变量名:"+monitoredItem.getNodeId().toString().replace("ns=2;s=",""));
//
//                }
//            };

    /**
     * 断开连接
     */
    public void disConnect(){
        if(uaClient.isConnected()){
            uaClient.disconnect();
        }
    }


    /**
     * 读取值
     * @param nodeId
     * @return
     */
    public DataValue readDataValue(NodeId nodeId){
        DataValue value=null;
        try {
            value = uaClient.readAttribute(nodeId, attributeId);
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client读取值失败，NodeId:"+nodeId+" 异常为："+e.getMessage());
        } catch (StatusException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client读取值失败，NodeId:"+nodeId+" 异常为："+e.getMessage());
        }

        return value;

    }


    /**
     * 写值
     * @param nodeId
     * @param value
     * @return
     */
    public boolean writeDataValue(NodeId nodeId,Object value){

        boolean status=false;
        try {
            status = uaClient.writeAttribute(nodeId, attributeId, value);
        } catch (ServiceException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client写值失败，NodeId:"+nodeId+" 异常为："+e.getMessage());
        } catch (StatusException e) {
            e.printStackTrace();
            XxlJobLogger.log("OPC Client写值失败，NodeId:"+nodeId+" 异常为："+e.getMessage());
        }

        return status;
    }

}
